#include "InterOpConvert.h"
#if 0
#include <SPAIopPSImportResult.h>
#include <SPAIopPolicy.h>
#include <SPAIopDocument.h>
#include <SPAIopProductStructureImporter.h>
#include "SPAIopAcisError.h"
#include <SPAIopOutputProductStructure.h>
#include <SPAIopProductStructureExporter.h>
#include <SPAIopResult.h>
#include <SPAIopPartDocument.h>
#include <lists.hxx>
#include <SPAIopOutputPSReference.h>
#include <SPAIopRep.h>
#include <SPAIopPartImportResult.h>
#include <SPAIopVisuData.h>
#include "SPAIopSampleUtilities.h"
#include "SPAIopAcisSampleUtilities.h"
#include "SPAIopSampleResultUtils.h"
#include "SPAIopPMIData.h"
#include "SPAIopMappingData.h"
#include "SPAIopAcisPartImportResult.h"
#include "SPAIopDocumentDataImporter.h"
#include "SPAIopDocumentDataImportResult.h"
//MP
#include "SPAIopMultiProcessBatchDefinition.h"
#include "SPAIopMultiProcessSpoolerEx.h"

#include "common/logger.h"
#include "progress/InterOpProgressReporter.h"
#include "utils/FileUtil.h"
#include "utils/StringUtil.h"
#include "unordered_map"
#include "InterOpUtils.h"
#include "InterOpMetaDataExporter.h"
#include <list>
#include <kernapi.hxx>
#include "InterOpDocumentUtils.h"
#include "SPAIopProductStructureUtils.h"

AMCAX_EXCHANGE_NAMESPACE_BEGIN

namespace InterOp {
    int DefineOutputProductStructure(const SPAIopInputProductStructure &iInputPS, SPAIopPSImportResult &iInputPSResult, const SPAIopPolicy &iPolicy, SPAIopOutputProductStructure &oOutputPS, int &opCopiedInstances);
    void CreateOutputProductStructureSkeletonRootInstance(const SPAIopInputProductStructure &iInputPS, 
            SPAIopPSImportResult &iInputPSResult, SPAIopOutputProductStructure &oOutputPS, 
            std::unordered_map<int, SPAIopOutputPSReference *> &iIdToOutputPSRef, 
            std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, 
            std::list<SPAIopOutputPSInstance *>>> &iRefIDToListOfAllInOutInst);
    void CreateOutputProductStructureSkeleton(
            SPAIopInputPSReference &iInputReference, SPAIopInputPSMissingFileIter &iMissingFiles, 
            SPAIopOutputProductStructure &iOutputPS, SPAIopOutputPSReference &oOutputReference, 
            std::unordered_map<int, SPAIopOutputPSReference *> &iIdToOutputPSRef, 
            std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, 
            std::list<SPAIopOutputPSInstance *>>> &iRefIDToListOfAllInOutInst);
    int DefinePSReference(
            SPAIopInputPSReference &iInputReference, SPAIopInputPSMissingFileIter &iMissingFiles, 
            SPAIopOutputProductStructure &iOutputPS, const SPAIopPolicy &iPolicy, 
            SPAIopOutputPSReference &oOutputReference, int &opNumDefined, 
            std::unordered_map<int, std::pair<bool, double[3]>> &iRefIDToShiftTranslationMap, 
            bool &oIsShiftTranslationVectorApplied, double (&oShiftTranslation)[3]);
    int ImportPart(SPAIopInputPSReference &iInputReference, const SPAIopPolicy &iPolicy, ENTITY_LIST &oAcisEntities, SPAIopPartImportResult &oPartImportResult);
    void ImportVisu(SPAIopPartDocument &iAcisInteropPartDocument, const SPAIopPolicy &iPolicy, SPAIopVisuData &oVisuData, bool &oIsShiftTranslationVectorApplied, double (&oShiftTranslation)[3]);
    void ImportPartVisu(SPAIopPartDocument& iAcisInteropPartDocument, const SPAIopPolicy& iPolicy, SPAIopVisuData& oVisuData);
    
        
    // License file
    // #include "spatial_license.h"
    #include "spa_unlock_result.hxx"
    static bool _g_transferPMIData = true;
    static bool _g_doingVisualization = false;
    static bool _g_transferMiscData = true;
    static std::vector<ENTITY_LIST*> _g_toBeReleased;

    //-----------------------------------------------------------------------------------------

    void MyProgressCallBack(double iProgress,bool& iAbort)
    {
        InterOp::Reporter::InterOpProgressReporter::GetInstance().ProgressCallBack(iProgress, iAbort);
    }

    //-----------------------------------------------------------------------------------------

    void MyProgressCallBackWithUserData(double iProgress, bool& iAbort, void* ipUserData_dt)
    {
        InterOp::Reporter::InterOpProgressReporter::GetInstance().ProgressCallBackWithUserData(iProgress, iAbort, ipUserData_dt);
    }
    
    int DefineOutputProductStructure(const SPAIopInputProductStructure &iInputPS, SPAIopPSImportResult &iInputPSResult, const SPAIopPolicy &iPolicy, SPAIopOutputProductStructure &oOutputPS, int &opCopiedInstances)
    {
        // Navigate all of the nodes of the input product structure and create the corresponding output product
        // structure (SPAIopOutputProductStructure) and its out instances (SPAIopOutputPSInstance) and output references (SPAIopOutputPSReference)
    
        bool success = true;
        int result = SPAIopSampleResult_Import_Ok;
        SPAIopInputPSMissingFileIter missingFiles = iInputPSResult.GetMissingFileIterator();
    
        SPAIopWString productName = iInputPS.GetProductName();
        oOutputPS.SetProductName( productName );
        if( _g_transferMiscData)
        {
            SPAIopPSReferenceMiscData rootMiscData = iInputPS.GetMiscData();
    
            SPAIopMiscOutputAsmData refMiscData;
            TransferAssemblyReferenceMiscData( rootMiscData, refMiscData);
            oOutputPS.SetRootMiscData( refMiscData);
        }
    
        SPAIopWString productPID;
        bool hasPID = iInputPS.GetPID(productPID);
        if(hasPID)
        {
            const wchar_t* prodName = NULL;
            productPID.ToWChar( prodName );
            SPAIopOutputPSReference* noRef = NULL;
            TrackReferences (set_root, prodName, noRef);
        }
    
        //Multiple instances having same reference will need below map to store shift translation vector for each unique reference
        //This is done as we don't process already defined/processed reference again
        std::unordered_map<int, std::pair<bool, double[3]>> refIDToShiftTranslationMap;
        SPAIopWorldOrientation orientation = SPAIopWorldOrientation_Unknown;
        iInputPS.GetWorldOrientation(orientation);
        oOutputPS.SetWorldOrientation(orientation);
    
    
        int nbInstances = iInputPS.GetRootInstancesCount();
        int copiedInstances = 0;
        for( int i = 0; i < nbInstances && true == success; i++ )
        {
            // Get Input PS info (instance, reference, ID)
            SPAIopInputPSInstance inputRootInstance = iInputPS.GetRootInstance(i);
    
            bool skipInstance = ShouldInstanceSkipped( inputRootInstance );
            if( true == skipInstance )
            {
                continue;
            }
    
            SPAIopInputPSReference inputReference = inputRootInstance.GetReference();
            SPAIopWString referenceStorageName = inputReference.GetStorageName();
            bool isMissingReference = missingFiles.ContainsPath( referenceStorageName );
            if ( false == isMissingReference )
            {
                unsigned int refID = inputReference.GetUniqueID();
    
                // Replicate the same on outPS
                SPAIopOutputPSReference outputReference(oOutputPS, refID); // refID is unique and used to identify the reference
    
                int numDefined = 0;
                bool isShiftTranslationVectorApplied = false;
                double shiftTranslationVector[3] = { 0.0 };
                int res = DefinePSReference(inputReference, missingFiles, oOutputPS, iPolicy, outputReference, numDefined, refIDToShiftTranslationMap, isShiftTranslationVectorApplied, shiftTranslationVector);
                if (0 != res)
                    result = res;
                if( numDefined > 0 )
                {
                    copiedInstances += numDefined;
    
                    SPAIopOutputPSInstance outputInstance(outputReference);
                    DefinePSInstance(inputRootInstance, outputInstance);
    
                    if (isShiftTranslationVectorApplied)
                    {
                        ModifyInstanceTransform(inputRootInstance, shiftTranslationVector, outputInstance);
                        refIDToShiftTranslationMap[refID].first = isShiftTranslationVectorApplied;
                        std::copy(std::begin(shiftTranslationVector), std::end(shiftTranslationVector), std::begin(refIDToShiftTranslationMap[refID].second));
                    }
                    else
                    {
                        //Case where reference is already defined but have shift translation vector
                        const auto unorderedMapIterator = refIDToShiftTranslationMap.find(refID);
                        if (unorderedMapIterator != refIDToShiftTranslationMap.end())
                        {
                            isShiftTranslationVectorApplied = unorderedMapIterator->second.first;
                            if (isShiftTranslationVectorApplied)
                            {
                                shiftTranslationVector[0] = unorderedMapIterator->second.second[0];
                                shiftTranslationVector[1] = unorderedMapIterator->second.second[1];
                                shiftTranslationVector[2] = unorderedMapIterator->second.second[2];
                                ModifyInstanceTransform(inputRootInstance, shiftTranslationVector, outputInstance);
                            }
                        }
                    }
    
                    oOutputPS.AddToRoot(outputInstance);
                }
            }
        }
        opCopiedInstances = copiedInstances;
    
        refIDToShiftTranslationMap.clear();
    
        return result;
    }
  
    //-----------------------------------------------------------------------------------------
    
    int DefinePSReference(SPAIopInputPSReference& iInputReference, SPAIopInputPSMissingFileIter& iMissingFiles, SPAIopOutputProductStructure& iOutputPS, const SPAIopPolicy& iPolicy, SPAIopOutputPSReference& oOutputReference, int& opNumDefined, std::unordered_map<int, std::pair<bool, double[3]>>& iRefIDToShiftTranslationMap, bool& oIsShiftTranslationVectorApplied, double(&oShiftTranslation)[3])
    {
        int numDefined = 0;
        int result = SPAIopSampleResult_Import_Ok;
        if( !oOutputReference.IsDefined() )
        {
            if( iInputReference.IsPart() )    // part node
            {
                // import the document
                ENTITY_LIST* iopAcisEntities = ACIS_NEW ENTITY_LIST;
                SPAIopPartImportResult partImportResult;
                result = ImportPart(iInputReference, iPolicy, *iopAcisEntities, partImportResult);
    
                bool addedDataToPartRef = false;
                if (iopAcisEntities && iopAcisEntities->count() > 0)
                {
                    SPAIopAcisOutputPart partData;
                    partData.SetPartEntities(*iopAcisEntities);
    
                    oIsShiftTranslationVectorApplied = partImportResult.GetShiftTranslation(oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
                    if (oIsShiftTranslationVectorApplied)
                    {
                        PrintMessage(L"\nGeometry is shifted near the origin to fit it inside the current modeler range, Following Shift Translation Vector should be applied on the part to get the original input coordinates of geometry:- X: %lf\tY: %lf\tZ: %lf\n", oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
                    }
    
                    const wchar_t* pNewTargetUnit = NULL;
                    SPAIopAcisPartImportResult* acisPartImportResult = static_cast<SPAIopAcisPartImportResult*>(&partImportResult);
                    if (NULL != acisPartImportResult)
                    {
                        const SPAIopLengthUnitType newTargetUnit = acisPartImportResult->GetUnit();
                        if (SPAIopLengthUnit_Unknown != newTargetUnit)
                        {
                            SPAIopWString unitStr = SPAIopLengthUnitUtils::LengthUnitToString(newTargetUnit);
                            const wchar_t* pUnitArray = NULL;
                            unitStr.ToWChar(pUnitArray);
                            PrintMessage(L"\nTarget Unit Modified to fit the geometry inside current modeler range, New Target Unit applied on the part:- %ls\n", pUnitArray);
                            //Setting modified target unit on output part
                            partData.SetPartUnit(newTargetUnit);
    
                            //Converting shift translation vector to product structure unit if target unit is modified, so that new transform gets calculated correctly
                            if (oIsShiftTranslationVectorApplied)
                            {
                                double unitScale = 1.0;
                                SPAIopLengthUnitType productStructureUnit = SPAIopLengthUnit_Unknown;
                                SPAIopModelerOptionsAcis acisModelerOptions;
                                const bool res = iPolicy.GetCurrentModelerOptions(acisModelerOptions);
                                if (res)
                                {
                                    const bool foundOption = acisModelerOptions.GetCurrentModelerUnits(productStructureUnit);
                                    if (!foundOption)
                                        productStructureUnit = SPAIopLengthUnit_Millimeter;
                                }
                                else
                                    productStructureUnit = SPAIopLengthUnit_Millimeter;
    
                                unitScale = SPAIopLengthUnitUtils::GetScalingFactor(newTargetUnit, productStructureUnit);
                                oShiftTranslation[0] = oShiftTranslation[0] * unitScale;
                                oShiftTranslation[1] = oShiftTranslation[1] * unitScale;
                                oShiftTranslation[2] = oShiftTranslation[2] * unitScale;
                            }
                        }
                    }
                    
                    if( _g_transferMiscData)
                        TransferPartReferenceMiscData(iInputReference, partImportResult, partData);
    
                    oOutputReference.SetOutputPartData( partData);
    
                    if (_g_transferPMIData)
                    {
                        SPAIopPMIData pmiData = partImportResult.GetPMIData();
                        partData.SetPMIData(pmiData);
                        SPAIopMappingData mappingData = partImportResult.GetMappingData();
                        partData.SetMappingData(mappingData);
                    }
    
                    _g_toBeReleased.push_back(iopAcisEntities);
                    addedDataToPartRef = true;
                }
                else
                {
                    PrintMessage(L"No Entities\n");
                }
                SPAIopPartDocument refPartDocument = iInputReference.GetDocument();
                // import the visualization, if requested, and directly attach it to the outputPS
                if (_g_doingVisualization)
                {
                    SPAIopVisuData iopVisuData;
                    ImportVisu( refPartDocument, iPolicy, iopVisuData, oIsShiftTranslationVectorApplied, oShiftTranslation);
                    oOutputReference.SetVisualization(iopVisuData);
                    addedDataToPartRef = true;
                }
    
                if (true == addedDataToPartRef)
                {
                    numDefined += 1;
                }
            }
            else   // sub-assembly node
            {
                SPAIopWString referencePID;
                bool hasPID = iInputReference.GetPID(referencePID);
                if(hasPID)
                {
                    const wchar_t* refName = NULL;
                    referencePID.ToWChar( refName );
                    SPAIopOutputPSReference* pReference = &oOutputReference;
                    TrackReferences (add_subassembly, refName, pReference);
                }
    ;
                int nbInstances=iInputReference.GetInstancesCount();
                if (nbInstances > 0) {
                    for( int i = 0; i < nbInstances; i++ )
                    {
                        // Get Input Product Structure info (instance, reference, ID)
                        SPAIopInputPSInstance iNodeInstance = iInputReference.GetInstance(i);
    
                        bool skipInstance = ShouldInstanceSkipped( iNodeInstance );
                        if( true == skipInstance )
                        {
                            continue;
                        }
    
                        SPAIopInputPSReference iNodeReference = iNodeInstance.GetReference();
                        SPAIopWString referenceStorageName = iNodeReference.GetStorageName();
                        bool isMissingReference = iMissingFiles.ContainsPath( referenceStorageName );
                        if ( false == isMissingReference )
                        {
                            unsigned int refID = iNodeReference.GetUniqueID();
    
                            // Replicate the same for the output product structure
                            SPAIopOutputPSReference newOutputReference(iOutputPS, refID);
                            int entities = 0;
                            bool isShiftTranslationVectorApplied = false;
                            double shiftTranslationVector[3] = { 0.0 };
                            int res = DefinePSReference(iNodeReference, iMissingFiles, iOutputPS, iPolicy, newOutputReference, entities, iRefIDToShiftTranslationMap, isShiftTranslationVectorApplied, shiftTranslationVector);// Recursively navigate nodes below this sub-assembly node
                            if (0 != res)
                                result = res;
                            if( entities > 0 )
                            {
                                SPAIopOutputPSInstance iOutputInstance(newOutputReference);
                                DefinePSInstance(iNodeInstance, iOutputInstance);
    
                                if (isShiftTranslationVectorApplied)
                                {
                                    ModifyInstanceTransform(iNodeInstance, shiftTranslationVector, iOutputInstance);
                                    iRefIDToShiftTranslationMap[refID].first = isShiftTranslationVectorApplied;
                                    std::copy(std::begin(shiftTranslationVector), std::end(shiftTranslationVector), std::begin(iRefIDToShiftTranslationMap[refID].second));
                                }
                                else
                                {
                                    //Case where reference is already defined but have shift translation vector
                                    const auto unorderedMapIterator = iRefIDToShiftTranslationMap.find(refID);
                                    if (unorderedMapIterator != iRefIDToShiftTranslationMap.end())
                                    {
                                        isShiftTranslationVectorApplied = unorderedMapIterator->second.first;
                                        if (isShiftTranslationVectorApplied)
                                        {
                                            shiftTranslationVector[0] = unorderedMapIterator->second.second[0];
                                            shiftTranslationVector[1] = unorderedMapIterator->second.second[1];
                                            shiftTranslationVector[2] = unorderedMapIterator->second.second[2];
                                            ModifyInstanceTransform(iNodeInstance, shiftTranslationVector, iOutputInstance);
                                        }
                                    }
                                }
    
                                oOutputReference.AddInstance(iOutputInstance);
                                numDefined += entities;
                            }
                        }
                    }
                }
                if( _g_transferMiscData)
                {
                    SPAIopPSReferenceMiscData asmReferenceMiscData = iInputReference.GetMiscData();
                    SPAIopMiscOutputAsmData refOutputMiscData;
                    TransferAssemblyReferenceMiscData( asmReferenceMiscData, refOutputMiscData);
                    oOutputReference.SetOutputSubAsmLevelMiscData( refOutputMiscData);
                }
            }
    
            // In some formats supporting delayed part loading, we do not get reference name without loading (importing) part first. So set it after part is imported.
            SPAIopWString referenceName = iInputReference.GetReferenceName();
            oOutputReference.SetName( referenceName );
    
            // mark this reference as fully defined so we don't go through this code again if it is used by another instance
            if (numDefined > 0) 
            {
                oOutputReference.SetDefined(true);
            }
        } 
        else 
        {
            // Don't do anything for previously defined refs, but pretend they were defined this time
            numDefined = 1;
        }
    
        opNumDefined = numDefined;
        return result;
    }
    
    //-----------------------------------------------------------------------------------------
    
    int ImportPart(SPAIopInputPSReference& iInputReference, const SPAIopPolicy& iPolicy, ENTITY_LIST& oAcisEntities, SPAIopPartImportResult& oPartImportResult)
    {
        // Import the brep data of a part into the given AcisEntities using the Import 
        // method of a SPAIopPartDocument object.  Use try/catch exception handling blocks to 
        // manage fatal errors.
        int result = SPAIopSampleResult_Import_Ok;
        SPAIopAcisPartImporter partImporter;
        SPAIopPartImportResult partImportResult;
    
        unsigned int uniqueID = iInputReference.GetUniqueID();
        SPAIopWString storageNamePath = iInputReference.GetStorageName();
    
        SPAIopWString storageName;
        SPAIopSampleStringUtils::GetNameWithExtension(storageNamePath, storageName);
    
        const wchar_t *pStorageName = NULL;
        storageName.ToWChar(pStorageName);
    
        try
        {
            PrintMessage(L"\n Processing reference part. UniqueID = %d, Storage Name = %ls \n", uniqueID, pStorageName);
    
            SPAIopPartDocument iopPartDocument = iInputReference.GetDocument();
            partImportResult = partImporter.ImportWithPolicy(iPolicy, iopPartDocument, oAcisEntities);
    
            // NOTE: The reference name is only valid after the part's import operation is successful.
            SPAIopWString referenceName = iInputReference.GetReferenceName();
            const wchar_t *pReferenceName = NULL;
            referenceName.ToWChar(pReferenceName);
    
            PrintMessage(L"\n Processed reference part. Reference Name = %ls \n", pReferenceName);
        }
        catch (SPAIopAcisError &error)
        {
            SPAIopAcisPrintUtils::DisplayError(error);
            PrintMessage(L"\n FAILURE : Part failed to import. UniqueID = %d, Storage Name = %ls \n", uniqueID, pStorageName);
            result = SPAIopSampleResult_Import_Part_Fail;
            oAcisEntities.clear();
        }
        catch (...)
        {
            PrintMessage(L"\n FAILURE : Part failed to import. UniqueID = %d, Storage Name = %ls \n", uniqueID, pStorageName);
            result = SPAIopSampleResult_Import_Part_Fail;
        }
    
        oPartImportResult = partImportResult;
        return result;
    }
    
    void CreateOutputProductStructureSkeleton(SPAIopInputPSReference &iInputReference, SPAIopInputPSMissingFileIter &iMissingFiles, SPAIopOutputProductStructure &iOutputPS, SPAIopOutputPSReference &oOutputReference, std::unordered_map<int, SPAIopOutputPSReference *> &iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance *>>> &iRefIDToListOfAllInOutInst)
    {
        if (!oOutputReference.IsDefined())
        {
            if (!iInputReference.IsPart())
            {
                SPAIopWString referencePID;
                bool hasPID = iInputReference.GetPID(referencePID);
                if (hasPID)
                {
                    const wchar_t *refName = NULL;
                    referencePID.ToWChar(refName);
                    SPAIopOutputPSReference *pReference = &oOutputReference;
                    TrackReferences(add_subassembly, refName, pReference);
                }
    
                int nbInstances = iInputReference.GetInstancesCount();
                if (nbInstances > 0)
                {
                    int i = 0;
                    for (i = 0; i < nbInstances; i++)
                    {
                        // Get Input Product Structure info (instance, reference, ID)
                        SPAIopInputPSInstance iNodeInstance = iInputReference.GetInstance(i);
    
                        bool skipInstance = ShouldInstanceSkipped(iNodeInstance);
                        if (true == skipInstance)
                        {
                            continue;
                        }
    
                        SPAIopInputPSReference iNodeReference = iNodeInstance.GetReference();
                        SPAIopWString referenceStorageName = iNodeReference.GetStorageName();
                        bool isMissingReference = iMissingFiles.ContainsPath(referenceStorageName);
                        if (false == isMissingReference)
                        {
                            unsigned int refID = iNodeReference.GetUniqueID();
    
                            // Replicate the same on outPS
                            SPAIopOutputPSReference *newOutputReference = NULL;
    
                            // If RefID entry already present in map get that reference
                            if (iIdToOutputPSRef.find(refID) != iIdToOutputPSRef.end())
                                newOutputReference = iIdToOutputPSRef[refID];
                            else
                                newOutputReference = new SPAIopOutputPSReference(iOutputPS, refID); // refID is unique and used to identify the reference
    
                            iIdToOutputPSRef.emplace(refID, newOutputReference);
                            SPAIopOutputPSInstance *iOutputInstance = new SPAIopOutputPSInstance(*newOutputReference);
                            DefinePSInstance(iNodeInstance, *iOutputInstance);
                            iRefIDToListOfAllInOutInst[refID].first.push_back(iNodeInstance);
                            iRefIDToListOfAllInOutInst[refID].second.push_back(iOutputInstance);
    
                            oOutputReference.AddInstance(*iOutputInstance);
                            CreateOutputProductStructureSkeleton(iNodeReference, iMissingFiles, iOutputPS, *newOutputReference, iIdToOutputPSRef, iRefIDToListOfAllInOutInst);
                        }
                    }
                }
            }
        }
    }
    
    void CreateOutputProductStructureSkeletonRootInstance(const SPAIopInputProductStructure &iInputPS, SPAIopPSImportResult &iInputPSResult, SPAIopOutputProductStructure &oOutputPS, std::unordered_map<int, SPAIopOutputPSReference *> &iIdToOutputPSRef, std::unordered_map<int, std::pair<std::list<SPAIopInputPSInstance>, std::list<SPAIopOutputPSInstance *>>> &iRefIDToListOfAllInOutInst)
    {
        SPAIopInputPSMissingFileIter missingFiles = iInputPSResult.GetMissingFileIterator();
        int nbInstances = iInputPS.GetRootInstancesCount();
        int i = 0;
        for (i = 0; i < nbInstances; i++)
        {
            // Get Input PS info (instance, reference, ID)
            SPAIopInputPSInstance inputRootInstance = iInputPS.GetRootInstance(i);
    
            bool skipInstance = ShouldInstanceSkipped(inputRootInstance);
            if (true == skipInstance)
            {
                continue;
            }
    
            SPAIopInputPSReference inputReference = inputRootInstance.GetReference();
            SPAIopWString referenceStorageName = inputReference.GetStorageName();
            bool isMissingReference = missingFiles.ContainsPath(referenceStorageName);
            if (false == isMissingReference)
            {
                unsigned int refID = inputReference.GetUniqueID();
    
                // Replicate the same on outPS
                SPAIopOutputPSReference *outputReference = NULL;
    
                // If RefID entry already present in map get that reference
                if (iIdToOutputPSRef.find(refID) != iIdToOutputPSRef.end())
                    outputReference = iIdToOutputPSRef[refID];
                else
                    outputReference = new SPAIopOutputPSReference(oOutputPS, refID); // refID is unique and used to identify the reference
    
                iIdToOutputPSRef.emplace(refID, outputReference);
                SPAIopOutputPSInstance *outputInstance = new SPAIopOutputPSInstance(*outputReference);
                DefinePSInstance(inputRootInstance, *outputInstance);
                iRefIDToListOfAllInOutInst[refID].first.push_back(inputRootInstance);
                iRefIDToListOfAllInOutInst[refID].second.push_back(outputInstance);
    
                oOutputPS.AddToRoot(*outputInstance);
                CreateOutputProductStructureSkeleton(inputReference, missingFiles, oOutputPS, *outputReference, iIdToOutputPSRef, iRefIDToListOfAllInOutInst);
            }
        }
    }
    void ImportVisu(SPAIopPartDocument &iAcisInteropPartDocument, const SPAIopPolicy &iPolicy, SPAIopVisuData &oVisuData, bool &oIsShiftTranslationVectorApplied, double (&oShiftTranslation)[3])
    {
        // Import the visualization data of a part into the given SPAIopVisuData using the Import
        // method of a SPAIopVisualizationImporter object.  Use try/catch exception handling blocks to
        // manage fatal errors.
    
        SPAIopVisualizationImporter visuImporter;
        try
        {
            SPAIopVisuImportResult importRes = visuImporter.ImportWithPolicy(iPolicy, iAcisInteropPartDocument);
    
            oVisuData = importRes.GetVisualizationData();
    
            const SPAIopLengthUnitType visualizationUnit = oVisuData.GetUnits();
            if (visualizationUnit != SPAIopLengthUnit_Unknown)
            {
                SPAIopWString unitStr = SPAIopLengthUnitUtils::LengthUnitToString(visualizationUnit);
                const wchar_t *pUnitArray = NULL;
                unitStr.ToWChar(pUnitArray);
                PrintMessage(L"\nUnit of Visualization Data:- %ls\n", pUnitArray);
            }
    
            if (!oIsShiftTranslationVectorApplied)
            {
                oIsShiftTranslationVectorApplied = importRes.GetShiftTranslation(oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
                if (oIsShiftTranslationVectorApplied)
                {
                    PrintMessage(L"\nGeometry is shifted near the origin to fit it inside the current modeler range, Following Shift Translation Vector should be applied on the part to get the original input coordinates of geometry:- X: %lf\tY: %lf\tZ: %lf\n", oShiftTranslation[0], oShiftTranslation[1], oShiftTranslation[2]);
    
                    // Scaling Shift translation vector to modeler unit as we will accomodate it as instance transform, instance transform is given in modeler unit
                    double unitScale = 1.0;
                    SPAIopLengthUnitType modelerUnit = SPAIopLengthUnit_Unknown;
                    SPAIopModelerOptionsAcis acisModelerOptions;
                    const bool res = iPolicy.GetCurrentModelerOptions(acisModelerOptions);
                    if (res)
                    {
                        const bool foundOption = acisModelerOptions.GetCurrentModelerUnits(modelerUnit);
                        if (!foundOption)
                            modelerUnit = SPAIopLengthUnit_Millimeter;
                    }
                    else
                        modelerUnit = SPAIopLengthUnit_Millimeter;
    
                    unitScale = SPAIopLengthUnitUtils::GetScalingFactor(visualizationUnit, modelerUnit);
                    oShiftTranslation[0] = oShiftTranslation[0] * unitScale;
                    oShiftTranslation[1] = oShiftTranslation[1] * unitScale;
                    oShiftTranslation[2] = oShiftTranslation[2] * unitScale;
                }
            }
        }
        catch (SPAIopAcisError &error)
        {
            SPAIopAcisPrintUtils::DisplayError(error);
        }
        catch (...)
        {
            std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
        }
    }
    void ImportPartVisu(SPAIopPartDocument& iAcisInteropPartDocument, const SPAIopPolicy& iPolicy, SPAIopVisuData& oVisuData)
    {
        // Import the visualization data of a part into the given SPAIopVisuData using the Import 
        // method of a SPAIopVisualizationImporter object.  Use try/catch exception handling blocks to 
        // manage fatal errors.

        SPAIopVisualizationImporter visuImporter;
        try
        {
            SPAIopVisuImportResult importRes = visuImporter.ImportWithPolicy(iPolicy, iAcisInteropPartDocument);

            oVisuData = importRes.GetVisualizationData();
        }
        catch (const SPAIopAcisError& error)
        {
            SPAIopAcisPrintUtils::DisplayError(error);
        }
        catch (...)
        {
            std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
        }
    }
        
    #include "core/InterOpUtils.h"
    // void DumpMissingFilesInProductStructure( const SPAIopPSImportResult& iPSImportResult )
    // {
    //     // View missing sub-assemblies and parts
    //     SPAIopInputPSMissingFileIter missingFiles = iPSImportResult.GetMissingFileIterator();
    //     while ( missingFiles.Next())
    //     {
    //         SPAIopWString missingFile = missingFiles.Current();
    //         const wchar_t* pMissingFileNameStr = NULL;
    //         missingFile.ToWChar( pMissingFileNameStr );
    //         if( pMissingFileNameStr )
    //         {
    //             PrintMessage( L"WARNING - Unable to locate missing file = %ls \n", pMissingFileNameStr );
    //         }			
    //     }
    // }

    size_t GetNonMissingParts(SPAIopInputProductStructure& iInputPSStructure, SPAIopPSImportResult &iInputPSResult)
    {
        size_t missingParts = 0;
        size_t totalParts = iInputPSStructure.GetUniqueReferencesCount();
        SPAIopInputPSMissingFileIter missingFiles = iInputPSResult.GetMissingFileIterator();   
        int nInstances=iInputPSStructure.GetUniqueReferencesCount();
        if (nInstances > 0)
        {
            int i=0;
            for( i = 0; i < nInstances; i++ )
            {
                
                SPAIopInputPSReference uniqueReference = iInputPSStructure.GetUniqueReference(i);
                SPAIopWString referenceStorageName = uniqueReference.GetStorageName();
                bool isMissingReference = missingFiles.ContainsPath( referenceStorageName );
                if ( true == isMissingReference )
                {
                    if(uniqueReference.IsPart())
                    {
                        missingParts++;
                    }

                }
            }
                    
        }
        return ( totalParts - missingParts );

    }
    
    void ImportFile(const std::string &file_path, SPAIopInputProductStructure &input_product_structure)
    {
        try{
            SPAIopWString wSourceFilePath = AMCAX::Exchange::InterOp::StringToSPAIopWString(file_path);
            SPAIopDocument sourceDoc(wSourceFilePath);

            SPAIopProductStructureImporter importer;
            importer.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);
            SPAIopPolicy iPolicy;
            SPAIopPSImportResult input_result = importer.ImportWithPolicy(iPolicy, sourceDoc, input_product_structure);

            DumpMissingFilesInProductStructure(input_result) ;
            
            InterOp::Reporter::InterOpProgressReporter::GetInstance().SetNumParts(input_product_structure.GetUniqueReferencesCount());
            size_t numNonMissingParts = GetNonMissingParts(input_product_structure,input_result);
            InterOp::Reporter::InterOpProgressReporter::GetInstance().setNonMissingParts( numNonMissingParts );
            InterOp::Reporter::InterOpProgressReporter::GetInstance().SetPsDone(true); 
        }
        catch (SPAIopAcisError &error)
        {
            SPAIopWString req = error.GetRequest();
            SPAIopWString diagnostic = error.GetDiagnostic();
            SPAIopWString advice = error.GetAdvice();
            
            LOG_INFO("Error Msg, req:{}, diagnostic:{}, advice:{}",
                AMCAX::Exchange::InterOp::SPAIopWStringToString(req),
                AMCAX::Exchange::InterOp::SPAIopWStringToString(diagnostic),
                AMCAX::Exchange::InterOp::SPAIopWStringToString(advice));
        }
        catch (...)
        {
            LOG_ERROR("FAILURE: import Unknown exception caught!");
        }
    }
    
    bool IsInputPartFile(const SPAIopInputProductStructure& iInputPS)
    {
        // Some code to check if input fle is single part file.
        int refCount = iInputPS.GetUniqueReferencesCount();
        if (refCount != 1)
            return false;

        SPAIopInputPSReference inputPSRef = iInputPS.GetUniqueReference(0);
        int instCount = iInputPS.GetRootInstancesCount();
        if (1 != instCount)
            return false;

        SPAIopInputPSInstance inputPartInstance = iInputPS.GetRootInstance(0);

        double affineTransform[3][3] = { { 1.0, 0.0, 0.0 },{ 0.0, 1.0, 0.0 },{ 0.0, 0.0, 1.0 } };
        double translation[3] = { 0. };
        inputPartInstance.GetInstancePositionMatrix(affineTransform, translation);
        bool isIdentity = true;
        for (int i = 0; i<3; i++)
        {
            for (int j = 0; j<3; j++)
            {
                double val = affineTransform[i][j];
                if (isIdentity)
                {
                    if (i == j && (std::fabs(val - 1.0) > 1e-10))
                        isIdentity = false;
                    else if (i != j && std::fabs(val) > 1e-10)
                        isIdentity = false;
                }
                else
                    break;
            }
            if (std::fabs(translation[i]) > 1e-10)
                isIdentity = false;
        }

        if (!isIdentity)
            return false;

        SPAIopInputPSReference inputPartPSRef = inputPartInstance.GetReference();
        if (!inputPartPSRef.IsPart())
            return false;

        return true;
    }

    void ExportPartFile(const std::string file_path, const SPAIopInputProductStructure &inputPS, const SPAIopPolicy& iPolicy)
    {
        std::string out_file_extend = FileUtil::GetExtensionWithoutDot(file_path);
        SPAIopWString wTargetDocType = AMCAX::Exchange::InterOp::StringToSPAIopWString(out_file_extend);
        SPAIopWString wTargetFilePath = AMCAX::Exchange::InterOp::StringToSPAIopWString(file_path);
        
        SPAIopInputPSInstance inputPartInstance = inputPS.GetRootInstance(0);
        SPAIopInputPSReference inputPartRef = inputPartInstance.GetReference();
        SPAIopPartDocument partReferneceDoc = inputPartRef.GetDocument();

        SPAIopAcisOutputPart outputPart;
        if (_g_doingVisualization)
        {
            SPAIopVisuData iopPartVisuData;
            ImportPartVisu(partReferneceDoc, iPolicy, iopPartVisuData);
            outputPart.SetVisualizationData(iopPartVisuData);
        }

        ENTITY_LIST* iopAcisEntities = ACIS_NEW ENTITY_LIST;
        SPAIopPartImportResult partImportResult;
        SPAIopAcisPartImporter partImporter;
        InterOp::Reporter::InterOpProgressReporter::GetInstance().SetExportStarts(false) ;
        partImporter.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);
        partImportResult = partImporter.ImportWithPolicy(iPolicy, partReferneceDoc, *iopAcisEntities);

        if (iopAcisEntities && iopAcisEntities->count() > 0)
        {
            outputPart.SetPartEntities(*iopAcisEntities);

            // transfer misc data
            if (_g_transferMiscData)
            {
                TransferPartReferenceMiscData(inputPartRef, partImportResult, outputPart);
            }

            // transfer pmi data
            if (_g_transferPMIData)
            {
                SPAIopPMIData pmiData = partImportResult.GetPMIData();
                outputPart.SetPMIData(pmiData);
                SPAIopMappingData mappingData = partImportResult.GetMappingData();
                outputPart.SetMappingData(mappingData);
            }

            _g_toBeReleased.push_back(iopAcisEntities);

            SPAIopPartExporter partExporter;
            try
            {
                InterOp::Reporter::InterOpProgressReporter::GetInstance().SetExportStarts(true) ;
                partExporter.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);
                SPAIopResult res = partExporter.ExportWithPolicy(iPolicy, outputPart, wTargetFilePath);
            }
            catch (const SPAIopAcisError& error)
            {
                SPAIopWString req = error.GetRequest();
                SPAIopWString diagnostic = error.GetDiagnostic();
                SPAIopWString advice = error.GetAdvice();
                
                LOG_INFO("Error Msg, req:{}, diagnostic:{}, advice:{}",
                    AMCAX::Exchange::InterOp::SPAIopWStringToString(req),
                    AMCAX::Exchange::InterOp::SPAIopWStringToString(diagnostic),
                    AMCAX::Exchange::InterOp::SPAIopWStringToString(advice));
            }
            catch (...)
            {
                std::wcout << "FAILURE: Unknown exception caught! " << std::endl;
            }
        }
    }

    void ExportFile(const std::string file_path, const SPAIopInputProductStructure &input_product_structure, const SPAIopPolicy& iPolicy)
    {
        SPAIopOutputProductStructure output_product_structure;
        std::string out_file_extend = FileUtil::GetExtensionWithoutDot(file_path);
        SPAIopWString wTargetDocType = AMCAX::Exchange::InterOp::StringToSPAIopWString(out_file_extend);
        SPAIopWString wTargetFilePath = AMCAX::Exchange::InterOp::StringToSPAIopWString(file_path);
        
        int numCopiedInstances = 0;
        SPAIopPSImportResult input_result;
        int ret = DefineOutputProductStructure(input_product_structure,
                                                input_result,
                                                iPolicy,
                                                output_product_structure,
                                                numCopiedInstances);
    
        // SPAIopPolicy iExportPolicy;
        SPAIopProductStructureExporter exporter;
        InterOp::Reporter::UserProgressCallbackClass localCallackClass;
        // if (_g_hasUserData)
        //     exporter.SetProgressCallBackWithUserData((SPAIopProgressCallbackWithUserData)MyProgressCallBackWithUserData, &localCallackClass);
        // else
        InterOp::Reporter::InterOpProgressReporter::GetInstance().SetExportStarts(true) ;
        exporter.SetProgressCallBack((SPAIopProgressCallback)MyProgressCallBack);
        SPAIopResult res = exporter.ExportWithPolicy(iPolicy, output_product_structure, wTargetFilePath);
    }

    void ExportMetaData(const std::string &inFilePath, const std::string &outFilePath, FacetOptions facetOptions, MetaDataInfo &info)
    {
        SPAIopInputProductStructure ps;
        ImportFile(inFilePath,ps) ;

        std::string fileExtension = StringUtil::ToLowerCase(FileUtil::GetExtension(outFilePath));
        std::string outJsonFilePath = outFilePath;
        if(fileExtension != ".json")
        {
            outJsonFilePath += ".json";
        }
        MetaDataExporter metaDataExporter(outJsonFilePath, facetOptions);
        metaDataExporter.Export(ps, info);
    }
    
    //-----------------------------------------------------------------------------------------
    
    
    void ConvertUseInterOp(const std::string &inFilePath,const std::string &outFilePath, const std::string &policyFilePath)
    {
        InterOp::Reporter::InterOpProgressReporter::GetInstance().Reset() ;
        api_start_modeller(0);        
        LOG_INFO("ConvertUseInterOp, inFilePath:{}, outFilePath:{}", inFilePath, outFilePath);

        SPAIopPolicy iPolicy;
        if(!policyFilePath.empty())
        {
            SPAIopWString wPolicyFilePath = AMCAX::Exchange::InterOp::StringToSPAIopWString(policyFilePath);
            SPAIopPolicyFileStreamXML xmlPolicyStream;
            if(xmlPolicyStream.SetUnstreamFilePath(wPolicyFilePath)){
                LOG_INFO("SetUnstreamFilePath, policyFilePath:{}", policyFilePath);
            }
            else{
                LOG_ERROR("SetUnstreamFilePath failed, policyFilePath:{}", policyFilePath);
            }
            
            iPolicy.Deserialize(xmlPolicyStream);
        }

        SPAIopInputProductStructure inputPS;
        SPAIopWString wSourceFilePath = AMCAX::Exchange::InterOp::StringToSPAIopWString(inFilePath);
        SPAIopDocument sourceDoc(wSourceFilePath);
        ImportFile(inFilePath, inputPS);


        // import document data
        SPAIopDocumentDataImportResult psRootDataResult;

        {
            SPAIopDocumentDataImporter psDataImporter;
            SPAIopPolicy iPolicy;
            psRootDataResult = psDataImporter.ImportWithPolicy(iPolicy, sourceDoc);
        }
        
        // check if document is part
        bool inputIsPartFile = IsInputPartFile(inputPS);
        if (true == inputIsPartFile)
        {
            SPAIopEntityIter entityIter = psRootDataResult.GetOccurrenceEntityIterator();
            int count = entityIter.GetCount();
            if (count > 0)
                inputIsPartFile = false; // real assembly
        }

        
        if (inputIsPartFile)
        {
            ExportPartFile(outFilePath, inputPS, iPolicy);
        }
        else
        {
            ExportFile(outFilePath, inputPS, iPolicy);
        }
    
        for (std::vector<ENTITY_LIST*>::iterator iter = _g_toBeReleased.begin(); iter != _g_toBeReleased.end(); ++iter)
        {
            SPAIopAcisSystem::ReleasePartEntities(**iter);
            ACIS_DELETE *iter;
        }
        _g_toBeReleased.clear();
        // int result(1) ;
        // SPAIopPolicy importPolicy;
        // SPAIopPolicy exportPolicy;   
        // const wchar_t* pSourceDocType = NULL;
        // const wchar_t* pTargetDocType = NULL;
        // const wchar_t* pSourceFilePath = StringToWString(inFilePath).c_str() ; 
        // const wchar_t* pTargetFilePath = StringToWString(outFilePath).c_str();
        // result = ImportAndExportModel(pSourceFilePath, pTargetFilePath, pSourceDocType, pTargetDocType, importPolicy, exportPolicy);
        // for (std::vector<ENTITY_LIST*>::iterator iter = toBeReleased.begin(); iter != toBeReleased.end(); ++iter)
        // {
        //     SPAIopAcisSystem::ReleasePartEntities(**iter);
        //     ACIS_DELETE *iter;
        // }
        // toBeReleased.clear();
        api_stop_modeller();
    }

    MetaDataInfo GetMetaDataUsingInterOp(const std::string &inFilePath, const std::string &outTopoJsonFilePath, const FacetOptions &facetOptions)
    {
        api_start_modeller(0);
        MetaDataInfo metaDataInfo;
        ExportMetaData(inFilePath, outTopoJsonFilePath, facetOptions, metaDataInfo);
        api_stop_modeller();
        return metaDataInfo;
    }
}


AMCAX_EXCHANGE_NAMESPACE_END

#endif